// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package utils

import (
	"fmt"
	"reflect"
	"testing"

	. "github.com/onsi/gomega"

	"github.com/openclarity/openclarity/scanner/families/malware/types"
)

func Test_extractDetectedMalware(t *testing.T) {
	type args struct {
		line *ScanResultLine
	}
	tests := []struct {
		name string
		args args
		want *types.Malware
	}{
		{
			name: "scan result line is nil",
			args: args{
				line: nil,
			},
			want: nil,
		},
		{
			name: "scan result line is not nil",
			args: args{
				line: &ScanResultLine{
					RuleName: "vmdetect",
					Metadata: map[string]string{
						"author":      "nex",
						"description": "Possibly employs anti-virtualization techniques",
					},
					Path: "/usr/bin/test",
				},
			},
			want: &types.Malware{
				Path:     "/usr/bin/test",
				RuleName: "vmdetect",
			},
		},
		{
			name: "rule name and type are set in metadata",
			args: args{
				line: &ScanResultLine{
					RuleName: "vmdetect",
					Metadata: map[string]string{
						"author":      "nex",
						"description": "Possibly employs anti-virtualization techniques",
						"name":        "name",
						"type":        "type",
					},
					Path: "/usr/bin/test",
				},
			},
			want: &types.Malware{
				Path:        "/usr/bin/test",
				RuleName:    "vmdetect",
				MalwareName: "name",
				MalwareType: "type",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := extractDetectedMalware(tt.args.line); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("extractDetectedMalware() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_parseYaraRuleMetadata(t *testing.T) {
	type args struct {
		metadata string
	}
	tests := []struct {
		name string
		args args
		want map[string]string
	}{
		{
			name: "sanity test",
			args: args{
				metadata: `author="_pusher_",description="Looks for big numbers 256:sized",date="2016-08"`,
			},
			want: map[string]string{
				"author":      "_pusher_",
				"description": "Looks for big numbers 256:sized",
				"date":        "2016-08",
			},
		},
		{
			name: "empty metadata ",
			args: args{
				metadata: "[]",
			},
			want: make(map[string]string),
		},
		{
			name: "empty string as metadata",
			args: args{},
			want: make(map[string]string),
		},
		{
			name: "no value defined for a metadata key",
			args: args{
				metadata: `author="_pusher_",description,date="2016-08"`,
			},
			want: map[string]string{
				"author": "_pusher_",
				"date":   "2016-08",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := parseYaraRuleMetadata(tt.args.metadata); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("parseYaraRuleMetadata() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_separateFields(t *testing.T) {
	type args struct {
		line string
	}
	tests := []struct {
		name string
		args args
		want *ScanResultLine
	}{
		{
			name: "missing [",
			args: args{
				line: `RijnDael_AES author="_pusher_",description="RijnDael AES test",date="2016-06" /bin/test`,
			},
			want: nil,
		},
		{
			name: "metadata without spaces",
			args: args{
				line: `RijnDael_AES [author="_pusher_",description="RijnDael_AES_test",date="2016-06"] /bin/test`,
			},
			want: &ScanResultLine{
				RuleName: "RijnDael_AES",
				Metadata: map[string]string{
					"author":      "_pusher_",
					"description": "RijnDael_AES_test",
					"date":        "2016-06",
				},
				Path: "/bin/test",
			},
		},
		{
			name: "metadata with spaces",
			args: args{
				line: `RijnDael_AES [author="_pusher_",description="RijnDael AES test spaces",date="2016-06"] /bin/test`,
			},
			want: &ScanResultLine{
				RuleName: "RijnDael_AES",
				Metadata: map[string]string{
					"author":      "_pusher_",
					"description": "RijnDael AES test spaces",
					"date":        "2016-06",
				},
				Path: "/bin/test",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := separateFields(tt.args.line); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("separateFields() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestParseYaraScanOutput(t *testing.T) {
	type args struct {
		line string
	}
	tests := []struct {
		name        string
		args        args
		want        *types.Malware
		expectedErr error
	}{
		{
			name: "sanity",
			args: args{
				line: `RijnDael_AES [author="_pusher_",description="RijnDael_AES_test",date="2016-06"] /bin/test`,
			},
			want: &types.Malware{
				MalwareName: "",
				MalwareType: "",
				Path:        "/bin/test",
				RuleName:    "RijnDael_AES",
			},
		},
		{
			name: "sanity with error in output",
			args: args{
				line: `error scanning /root/secret cannot open file`,
			},
			expectedErr: fmt.Errorf("yara scan failed: %s", `error scanning /root/secret cannot open file`),
		},
		{
			name: "invalid line",
			args: args{
				line: "invalid line",
			},
			expectedErr: fmt.Errorf("invalid yara line: %s", "invalid line"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)

			got, err := ParseYaraScanOutput(tt.args.line)
			if err != nil {
				g.Expect(err.Error()).Should(BeEquivalentTo(tt.expectedErr.Error()))
				return
			}
			if err == nil && tt.expectedErr != nil {
				t.Errorf("There is no error but should be, want err %v", tt.expectedErr)
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ParseYaraScanOutput() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestIsErrorThresholdReached(t *testing.T) {
	type args struct {
		errorCount uint
		allCount   uint
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "threshold is reached",
			args: args{
				errorCount: 9,
				allCount:   10,
			},
			want: true,
		},
		{
			name: "threshold is not reached",
			args: args{
				errorCount: 3,
				allCount:   10,
			},
			want: false,
		},
		{
			name: "there are no lines",
			args: args{
				errorCount: 0,
				allCount:   0,
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)

			actual := IsErrorThresholdReached(tt.args.errorCount, tt.args.allCount)
			g.Expect(actual).Should(BeEquivalentTo(tt.want))
		})
	}
}
